#include <math.h>
#include <stdlib.h>

#include "map.h"
#include "resources.h"
#include "mt.h"

int map_index (Map* m, int x, int y, int z) 
{
	assert ((x < m->width) && (y < m->height) && (z < m->num_layers));
	return z*m->width*m->height + y*m->width + x;
}

// Same as map_index, but with different assertions
int map_index_light (Map* m, int x, int y, int z)
{
	assert ((x < m->width * LIGHTTABLE_RES) && (y < m->height * LIGHTTABLE_RES) && (z < m->num_layers));
	return z*(m->width*LIGHTTABLE_RES)*(m->height*LIGHTTABLE_RES) + y*(m->width*LIGHTTABLE_RES) + x;
}

// Get pointer from coords
Tile* map_tile (Map* m, int x, int y, int z)
{
	return &m->tile[map_index (m,x,y,z)];
}

// Initializes a map with a single layer.
void map_init(Map* m, int w, int h)
{
	memset (m, 0, sizeof (Map));
	
	m->num_layers = 1;
	m->width = w;
	m->height = h;
	
	m->tile = malloc (m->num_layers * m->width * m->height * sizeof(Tile));
	m->lighttable = malloc (m->num_layers * (m->width * LIGHTTABLE_RES) * (m->height * LIGHTTABLE_RES) * sizeof(unsigned char*));
	m->tileset = malloc (m->num_layers * sizeof(BITMAP*));
	m->tile_mark = malloc (m->width * m->height * (m->num_layers+1) * sizeof (int));
	m->light_mark = malloc ((m->width * LIGHTTABLE_RES) * (m->height * LIGHTTABLE_RES) * (m->num_layers+1) * sizeof(int));
	
	memset (m->tile, 0, m->num_layers * m->width * m->height * sizeof(Tile));
	memset (m->lighttable, 0, m->num_layers * (m->width * LIGHTTABLE_RES) * (m->height * LIGHTTABLE_RES) * sizeof(unsigned char*));
	memset (m->tileset, 0, m->num_layers * sizeof(BITMAP*));
	lightmem_init(&m->lighttbl_pool, LIGHTMEMPOOL_SIZE, m->lighttable);
	map_unmark_tiles (m);
	map_unmark_light (m);
}

void map_cleanup (Map* m)
{
	free (m->tile);
	free (m->lighttable);
	free (m->tileset);
	free (m->tile_mark);
	free (m->light_mark);
	lightmem_free (&m->lighttbl_pool);
}

void map_extend (Map* m, int z)
{
	#if TOWERHACK_DEBUG
	fprintf (stderr, "[Debug] map_extend from %d layers to %d layers.\n", m->num_layers, z + 1);
	#endif
	//assert(z >= m->num_layers);
	int old_layers = m->num_layers;
	
	m->num_layers = z + 1;
	m->tile = realloc (m->tile, m->num_layers * m->width * m->height * sizeof(Tile));
	m->lighttable = realloc (m->lighttable, m->num_layers * (m->width * LIGHTTABLE_RES) * (m->height * LIGHTTABLE_RES) * sizeof(unsigned char*));
	m->tileset = realloc (m->tileset, m->num_layers * sizeof(BITMAP*));
	
	if (old_layers < m->num_layers) {
		void* begin = map_tile(m,0,0,old_layers);
		size_t memsize = (m->num_layers-old_layers) * m->width * m->height * sizeof(Tile);
		memset (begin, 0, memsize);
		memset (&m->tileset[old_layers-1], 0, (m->num_layers - old_layers) * sizeof(BITMAP*));
		const int LT_LAYER_SIZE = (m->width * LIGHTTABLE_RES) * (m->height * LIGHTTABLE_RES);
		memset (m->lighttable + old_layers * LT_LAYER_SIZE, 0, (m->num_layers - old_layers) * LT_LAYER_SIZE * sizeof(unsigned char*));
		m->tile_mark = realloc (m->tile_mark, m->width * m->height * (m->num_layers+1) * sizeof (int));
		m->light_mark = realloc (m->light_mark, m->width * m->height * (m->num_layers+1) * LIGHTTABLE_RES * LIGHTTABLE_RES * sizeof (int));
		map_unmark_tiles (m);
	}
}

void map_effect (Map* m, Effect e)
{
	m->effect[m->next_effect++ % MAX_EFFECTS] = e;
}

static void draw_subtile (BITMAP* tileset, BITMAP* target, int tx, int ty, int grid_size, int sx, int sy)
{
	masked_blit (tileset, target, tx * grid_size, ty * grid_size, sx, sy, grid_size, grid_size);
}

static void draw_tile (BITMAP* tileset, BITMAP* target, Tile* t, int sx, int sy, int anim_frame)
{
	switch (t->type) {

	case TILE_WALL: 
	case TILE_TOGGLEABLE: 
		draw_subtile (tileset, target, 4+ t->deco.subframe[0] % 4, t->deco.subframe[0] / 4, TILE_SIZE/2, sx              , sy);
		draw_subtile (tileset, target, 4+ t->deco.subframe[1] % 4, t->deco.subframe[1] / 4, TILE_SIZE/2, sx + TILE_SIZE/2, sy);
		draw_subtile (tileset, target, 4+ t->deco.subframe[2] % 4, t->deco.subframe[2] / 4, TILE_SIZE/2, sx              , sy + TILE_SIZE/2);
		draw_subtile (tileset, target, 4+ t->deco.subframe[3] % 4, t->deco.subframe[3] / 4, TILE_SIZE/2, sx + TILE_SIZE/2, sy + TILE_SIZE/2);
		break;

	case TILE_FLOOR:
	case TILE_DOOR:
	case TILE_TRIGGER:
	case TILE_EXIT_UP:
	case TILE_EXIT_DOWN:
	case TILE_EXIT_BETWEEN:
		draw_subtile (tileset, target, t->deco.frame % 9, t->deco.frame / 9, TILE_SIZE, sx, sy);
		break;
		
	case TILE_TELEPORTER:
		draw_subtile (tileset, target, t->deco.frame % 9 + anim_frame % 8, t->deco.frame / 9, TILE_SIZE, sx, sy);
		break;
	
	default: rectfill(target, sx, sy, sx+TILE_SIZE-1, sy+TILE_SIZE-1, 0); break;
	
	}
}

/**
 * Draws a tile-sized alpha-blended patch of 
 * darkness at the specified screen coordinates.
 */
static void apply_darkness (BITMAP* target, double darkness, int x, int y)
{
	int black = makeacol (0, 0, 0, darkness);
	const int W = TILE_SIZE / LIGHTTABLE_RES;
	rectfill (target, x, y, x+W-1, y+W-1, black);
}

/**
 * Draws the map tiles on screen
 */
void map_display(Map* m, BITMAP* target, coord_t player, int* xoffset, int* yoffset)
{	
	*xoffset = WIDTH/2  - player.x;
	*yoffset = HEIGHT/2 - player.y;

	int z = player.z;
	assert (z < m->num_layers);
	
	// bounds in tile units of on-screen visible map
	int min_x = MAX (0, (-(*xoffset) / TILE_SIZE));
	int min_y = MAX (0, (-(*yoffset) / TILE_SIZE));
	int max_x = MIN ( m->width , ((-(*xoffset)+SCREEN_W) / TILE_SIZE) +1 );
	int max_y = MIN ( m->height, ((-(*yoffset)+SCREEN_H) / TILE_SIZE) +1 );

	BITMAP* tileset = m->tileset[z];

	// Draw hi-res floor everywhere
	for (int y = min_y & (~1); y < max_y; y += 2)
	for (int x = min_x & (~1); x < max_x; x += 2) {
		masked_blit (tileset, target, 0, 0, (*xoffset) + x * TILE_SIZE, (*yoffset) + y * TILE_SIZE, TILE_SIZE*2, TILE_SIZE*2);
	}

	// Draw all the other tiles
	for (int y = min_y; y < max_y; y++)
	for (int x = min_x; x < max_x; x++) {
		Tile* t = map_tile(m,x,y,z);
		int px_x = (*xoffset) + x * TILE_SIZE;
		int px_y = (*yoffset) + y * TILE_SIZE;
		if (t->type != TILE_FLOOR) {
			draw_tile (tileset, target, t, px_x, px_y, m->anim_frame);
		}
	}
}

/**
 * Displays background effects. Effects in the foreground are drawn
 * together with map_display_fg.
 */
void map_display_effects (Map* m, BITMAP* target, coord_t player)
{
	int xoffset = WIDTH/2 - player.x;
	int yoffset = HEIGHT/2 - player.y;
	for (int i = 0; i < MAX_EFFECTS; i++) {
		if ((m->effect[i].cooldown > 0) && (m->effect[i].where.z == player.z)) {
			effect_draw_bg (&m->effect[i], target, xoffset, yoffset);
		}
	}
}

/**
 * Draw foreground map graphics
 * These include foreground wall tiles, additional foreground effects 
 * and the darkness layer.
 */
void map_display_fg (Map* m, BITMAP* target, coord_t player)
{
	int xoffset = WIDTH/2 - player.x;
	int yoffset = HEIGHT/2 - player.y;
	int z = player.z;
	assert (z < m->num_layers);

	// bounds in tile units of on-screen visible map
	int min_x = MAX (0, (-xoffset / TILE_SIZE));
	int min_y = MAX (0, (-yoffset / TILE_SIZE));
	int max_x = MIN ( m->width , ((-xoffset+SCREEN_W) / TILE_SIZE) +1 );
	int max_y = MIN ( m->height, ((-yoffset+SCREEN_H) / TILE_SIZE) +1 );

	BITMAP* tileset = m->tileset[z];

	// Draw all foreground decorations
	for (int y = min_y; y < max_y; y++)
	for (int x = min_x; x < max_x; x++) {
		Tile* t = map_tile(m,x,y,z);
		int xx = xoffset + x * TILE_SIZE;
		int yy = yoffset + y * TILE_SIZE;
		if (t->foreground[0]) draw_subtile (tileset, target, t->foreground[0] % 18, t->foreground[0] / 18, TILE_SIZE/2, xx, yy);
		if (t->foreground[1]) draw_subtile (tileset, target, t->foreground[1] % 18, t->foreground[1] / 18, TILE_SIZE/2, xx + TILE_SIZE/2, yy);
		if (t->foreground[2]) draw_subtile (tileset, target, t->foreground[2] % 18, t->foreground[2] / 18, TILE_SIZE/2, xx, yy + TILE_SIZE/2);
		if (t->foreground[3]) draw_subtile (tileset, target, t->foreground[3] % 18, t->foreground[3] / 18, TILE_SIZE/2, xx + TILE_SIZE/2, yy + TILE_SIZE/2);
	}
	
	// Foreground effects
	for (int i = 0; i < MAX_EFFECTS; i++) {
		if ((m->effect[i].cooldown > 0) && (m->effect[i].where.z == player.z)) {
			effect_draw_fg (&m->effect[i], target, xoffset, yoffset);
		}
	}
	
	// Darkness layer
	drawing_mode (DRAW_MODE_TRANS, NULL, 0, 0);
	set_alpha_blender();
	const int LIGHTSQUARE_SIZE = TILE_SIZE / LIGHTTABLE_RES;
	const double vx = ((double) player.x - 0.5) / LIGHTSQUARE_SIZE; // Viewer x in lightsquares
	const double vy = ((double) player.y - 0.5) / LIGHTSQUARE_SIZE; // Viewer y in lightsquares
	const double weight[4] = { // 1 - distance
		MAX(0, 1- (vx - floor(vx) + vy - floor(vy))),
		MAX(0, 1- (vx - floor(vx) +  ceil(vy) - vy)),
		MAX(0, 1- ( ceil(vx) - vx + vy - floor(vy))),
		MAX(0, 1- ( ceil(vx) - vx +  ceil(vy) - vy))
	};
	for (int y = min_y * LIGHTTABLE_RES; y < max_y * LIGHTTABLE_RES; y++) {
		for (int x = min_x * LIGHTTABLE_RES; x < max_x * LIGHTTABLE_RES; x++) {
			// The viewer position may lie in between tiles.
			// Therefore, compute a mean darkness value from all of them.
			int d[4] = {
				map_darkness(m, (coord_t) { floor(vx), floor(vy), z }, (coord_t) { x, y, z } ),
				map_darkness(m, (coord_t) { floor(vx),  ceil(vy), z }, (coord_t) { x, y, z } ),
				map_darkness(m, (coord_t) {  ceil(vx), floor(vy), z }, (coord_t) { x, y, z } ),
				map_darkness(m, (coord_t) {  ceil(vx),  ceil(vy), z }, (coord_t) { x, y, z } )
			};
			double weightsum = weight[0] + weight[1] + weight[2] + weight[3];
			double darkness = (weight[0]*d[0] + weight[1]*d[1] + weight[2]*d[2] + weight[3]*d[3]) / weightsum;
			if (darkness <= 0) continue;
		
			apply_darkness (target, darkness, xoffset + x * LIGHTSQUARE_SIZE, yoffset + y * LIGHTSQUARE_SIZE);
		}
	}
	solid_mode();
}

void map_decorate (Map* m) 
{		
	for (int z = 0; z < m->num_layers; z++) {
	for (int y = 0; y < m->height; y++)
	for (int x = 0; x < m->width; x++) {
		Tile* t = map_tile(m, x, y, z);
		int adj[8];
		
		#define IFWALL(x,y,z) ((((x) >= 0) && ((y) >= 0) && ((x) < m->width) && ((y) < m->height)) ? \
			(map_tile(m,(x),(y),(z))->type == TILE_WALL) || (map_tile(m,(x),(y),(z))->type == TILE_UNDEFINED) : 1)
	
		// Convert everything undefined to walls
		if (t->type == TILE_UNDEFINED) t->type = TILE_WALL;
		
		switch (t->type) {
			
		case TILE_WALL:
		case TILE_TOGGLEABLE:		
			adj[0] = IFWALL(x-1,y-1,z); 
			adj[1] = IFWALL(x  ,y-1,z);
			adj[2] = IFWALL(x+1,y-1,z);
			adj[3] = IFWALL(x-1,y  ,z);
			adj[4] = IFWALL(x+1,y  ,z); 
			adj[5] = IFWALL(x-1,y+1,z);
			adj[6] = IFWALL(x  ,y+1,z);
			adj[7] = IFWALL(x+1,y+1,z);
			// 0 1 2
			// 3   4
			// 5 6 7
				
			// subframe
			// 0 1
			// 2 3
			if (adj[3] && adj[4] && !adj[6]) {
				// Special case: use a full 16x16 wall
				t->deco.subframe[0] = 26;
				t->deco.subframe[1] = 27;
				t->deco.subframe[2] = 30;
				t->deco.subframe[3] = 31;
				break;
			}
			
			     if (!adj[3] && !adj[6])            t->deco.subframe[0] =  8;
			else if (!adj[3] &&  adj[6])            t->deco.subframe[0] =  4;
			else if ( adj[3] && !adj[5] &&  adj[6]) t->deco.subframe[0] = 11;
			else if ( adj[3] && !adj[6])            t->deco.subframe[0] =  9;
			else                                    t->deco.subframe[0] =  5;
			
			     if (!adj[4] && !adj[6])            t->deco.subframe[1] = 10;
			else if (!adj[4] &&  adj[6])            t->deco.subframe[1] =  6;
			else if ( adj[4] && !adj[7] &&  adj[6]) t->deco.subframe[1] =  3;
			else if ( adj[4] && !adj[6])            t->deco.subframe[1] =  9;
			else                                    t->deco.subframe[1] =  5;
			
			     if (!adj[6] && !adj[3])            t->deco.subframe[2] = 12;
			else if (!adj[6])                       t->deco.subframe[2] = 13;
			else if (!adj[3] &&  adj[6] &&  adj[5]) t->deco.subframe[2] = 15;
			else if (!adj[3] || !adj[5])            t->deco.subframe[2] =  4;
			else                                    t->deco.subframe[2] =  5;
			
			     if (!adj[6] && !adj[4])            t->deco.subframe[3] = 14;
			else if (!adj[6])                       t->deco.subframe[3] = 13;
			else if (!adj[4] &&  adj[6] &&  adj[7]) t->deco.subframe[3] =  7;
			else if (!adj[4] || !adj[7])            t->deco.subframe[3] =  6;
			else                                    t->deco.subframe[3] =  5;
			break;
			
		case TILE_EXIT_DOWN:
			if ((z > 0) && (map_tile(m, x, y, z-1)->type == TILE_EXIT_BETWEEN)) 
				t->deco.frame = 29; // Use ladder
			else
				t->deco.frame = 19; // Use stairs
			break;
			
		case TILE_EXIT_UP:
			if ((z < m->num_layers-1) && (map_tile(m, x, y, z+1)->type == TILE_EXIT_BETWEEN)) 
				t->deco.frame = 27; // Use ladder
			else
				t->deco.frame = 18; // Use stairs
			break;
		
		// FIXME: doors have adjacent wall tiles with extra deco etc.
		case TILE_DOOR:         t->deco.frame = 14; break;
		case TILE_EXIT_BETWEEN: t->deco.frame = 28; break;
		case TILE_TELEPORTER:   t->deco.frame = 36; break; 
		case TILE_TRIGGER:      t->deco.frame =  4; break;
		
		default:
			t->deco.frame = 0;
			break;
		
		}
		
		if (!IFWALL(x,y,z) && IFWALL(x,y+1,z)) {
			if (IFWALL(x-1,y+1,z)) t->foreground[2] = 5;
			else                   t->foreground[2] = 4;
			
			if (IFWALL(x+1,y+1,z)) t->foreground[3] = 5;
			else                   t->foreground[3] = 6;
		}
	}
	
	m->tileset[z] = bmp.tileset[mt_rand() % TILESET_COUNT];
	}
}

/**
 * Initializes a light table memory block. Single light tables may
 * then be extracted using lightmem_alloc. The bytes parameter
 * specifies how much memory the pool may use for both the actual 
 * light tables and the lookup table for their containers (tiles.)
 * The actual memory used may be less, but is never more than the 
 * given amount.
 */
void lightmem_init (Lightmem* pool, size_t bytes, unsigned char** map_lighttable)
{
	pool->capacity = bytes / (LIGHTTABLE_SIZE + sizeof(Tile*));
	pool->mem = malloc(pool->capacity * LIGHTTABLE_SIZE);
	pool->reverse_index = malloc(pool->capacity);
	pool->next = 0;
	pool->map_lighttable = map_lighttable;
	assert(pool->capacity > 0);
	assert(pool->mem);
	assert(pool->reverse_index);
}

/**
 * Returns a pointer to a block of memory from the light
 * memory pool that may then be used for a light table.
 */
unsigned char* lightmem_alloc (Lightmem* pool)
{
	int idx = pool->next % pool->capacity;
	
	// Memory used up? Kill an old light table entry
	fprintf(stderr, "pool->next: %d, pool->capacity: %d\n", pool->next, pool->capacity);
	if (pool->next >= pool->capacity)
		pool->map_lighttable[pool->reverse_index[idx]] = NULL;
		
	unsigned char* memptr = pool->mem + idx * LIGHTTABLE_SIZE;
	memset(memptr, 255, LIGHTTABLE_SIZE);
	pool->next++;
	return memptr;
}

/**
 * Releases the resources used in the light table pool.
 */
void lightmem_free (Lightmem* pool)
{
	free (pool->mem);
	free (pool->reverse_index);
	pool->capacity = 0;
	pool->mem = NULL;
	pool->reverse_index = NULL;
	pool->next = 0;
}

/**
 * Tests whether the lighting square's lighting table exists.
 * If not, calculates the lighting.
 */
unsigned char* require_square_lighting (Map* map, coord_t square)
{
	unsigned char** lt_entry = map->lighttable + map_index_light(map, square.x, square.y, square.z);
	unsigned char* lighttable = (*lt_entry);
	if (lighttable != NULL) return lighttable; // already calculated
	fprintf(stderr, "lighttable for (%d,%d,%d)\n", square.x, square.y, square.z);
	//Tile* viewer = map_tile (map, square.x / LIGHTTABLE_RES, square.y / LIGHTTABLE_RES, square.z);
	//if (viewer->lighttable != NULL) return; // already calculated
	
	lighttable = (*lt_entry) = lightmem_alloc (&map->lighttbl_pool);
	assert (lighttable);
	
	// bounds in lightsquare units of on-screen visible map
	int min_x = MAX(0, square.x - LIGHT_REACH);
	int min_y = MAX(0, square.y - LIGHT_REACH);
	int max_x = MIN((map->width * LIGHTTABLE_RES) - 1, square.x + LIGHT_REACH);
	int max_y = MIN((map->height * LIGHTTABLE_RES) - 1, square.y + LIGHT_REACH);
	
	typedef struct {
		coord_t c;
		double distance;
	} search_t;

	#define SEARCH_QUEUE_SIZE 512
	search_t search_queue [SEARCH_QUEUE_SIZE];
	int q_begin = 0;
	int q_end = 1;
	search_queue[0] = (search_t) { square, 0.0 };
	
	while (q_begin != q_end) {
		search_t s = search_queue[q_begin++ % SEARCH_QUEUE_SIZE];
		coord_t c = s.c;
		int lt_x = c.x - square.x + LIGHT_REACH; // Light table x coord
		int lt_y = c.y - square.y + LIGHT_REACH; // Light table y coord
		assert ((lt_x >= 0) && (lt_x < LIGHTTABLE_WH));
		assert ((lt_x >= 0) && (lt_y < LIGHTTABLE_WH));
		unsigned char* lte = lighttable + lt_y * LIGHTTABLE_WH + lt_x; // pointer to entry in tile's light table
		
		if (s.distance > LIGHT_REACH) { *lte = 255; continue; }
		else if (s.distance > FULLLIGHT_REACH)
			*lte = (s.distance - FULLLIGHT_REACH) * LIGHT_PER_TILE +.5;
		else *lte = 0;

		// returns true if this is a valid step for torchlight
		int step_valid (coord_t from, coord_t to)
		{
			//int dx = to.x - from.x;
			int dy = to.y - from.y;
			TileType tfrom = map_tile(map, from.x / LIGHTTABLE_RES, from.y / LIGHTTABLE_RES, to.z)->type;
			TileType tto   = map_tile(map,   to.x / LIGHTTABLE_RES,   to.y / LIGHTTABLE_RES, to.z)->type;
			if (tto == TILE_WALL) {
				if (dy == 1) {
					return 0;
				}
			}
			if (tfrom == TILE_WALL) {
				if (dy == -1) {
					TileType tfrom2 = map_tile(map, from.x / LIGHTTABLE_RES, (from.y + 1) / LIGHTTABLE_RES, to.z)->type;
					if (tfrom2 == TILE_WALL || tto != TILE_WALL) {
						return 0;
					}
				} else {
					return 0;
				}
			}
			
			// Prevents diagonal steps between 2 walls
			TileType t1 = map_tile(map, from.x / LIGHTTABLE_RES, to.y / LIGHTTABLE_RES, to.z)->type;
			TileType t2 = map_tile(map, to.x / LIGHTTABLE_RES, from.y / LIGHTTABLE_RES, to.z)->type;
			if ((t1 == TILE_WALL) && (t2 == TILE_WALL) && (tto != TILE_WALL))
				return 0;

			return 1;
		}

		void add_search (coord_t C, double distance)
		{
			int index = map_index_light(map, (C).x, (C).y, (C).z);
			int mark = map->light_mark[index]; 
			
			if ((mark == 0) || (mark > distance)) {
				map->light_mark[index] = distance;
				search_queue[q_end++ % SEARCH_QUEUE_SIZE] = (search_t) { C, distance }; 
				assert ((q_end - q_begin) <= SEARCH_QUEUE_SIZE); 
			}
		}

		coord_t next = c;
		if (c.x > min_x) { next.x--; if (step_valid(c, next)) add_search(next, s.distance+1.0); next = c; }
		if (c.x < max_x) { next.x++; if (step_valid(c, next)) add_search(next, s.distance+1.0); next = c; }
		if (c.y > min_y) { next.y--; if (step_valid(c, next)) add_search(next, s.distance+1.0); next = c; }
		if (c.y < max_y) { next.y++; if (step_valid(c, next)) add_search(next, s.distance+1.0); next = c; }
		if ((c.x > min_x) && (c.y > min_y)) { next.x--; next.y--; if (step_valid(c, next)) add_search(next, s.distance+M_SQRT2); next = c; }
		if ((c.x < max_x) && (c.y > min_y)) { next.x++; next.y--; if (step_valid(c, next)) add_search(next, s.distance+M_SQRT2); next = c; }
		if ((c.x > min_x) && (c.y < max_y)) { next.x--; next.y++; if (step_valid(c, next)) add_search(next, s.distance+M_SQRT2); next = c; }
		if ((c.x < max_x) && (c.y < max_y)) { next.x++; next.y++; if (step_valid(c, next)) add_search(next, s.distance+M_SQRT2); next = c; }
	}

	// DEBUG: output calculated lightmap
#if 0
	for (int y=0; y < LIGHTTABLE_WH; ++y) {
		for (int x=0; x < LIGHTTABLE_WH; ++x) {
			fprintf(stderr, "%x", lighttable[y * LIGHTTABLE_WH + x] / 0x10);
		}
		fprintf(stderr, "\n");
	}
#endif

	map_unmark_light (map);
	return lighttable;
}

unsigned char map_darkness (Map* m, coord_t viewer, coord_t target)
{
	if (viewer.z != target.z) return 255;
	if (abs(target.x - viewer.x) > LIGHT_REACH) return 255;
	if (abs(target.y - viewer.y) > LIGHT_REACH) return 255;
	
	unsigned char* lighttable = require_square_lighting (m, viewer);
	int xoffset = target.x - viewer.x + LIGHT_REACH;
	int yoffset = target.y - viewer.y + LIGHT_REACH;
	int offset = yoffset * LIGHTTABLE_WH + xoffset;
	return lighttable[offset];
}

void map_unmark_tiles_layer (Map* m, int z)
{
	assert (z < m->num_layers);
	memset (m->tile_mark + z * m->width * m->height, 0, m->width * m->height * sizeof (int));
}

void map_unmark_tiles (Map* m)
{
	memset (m->tile_mark, 0, m->width * m->height * (m->num_layers+1) * sizeof (int));
}

void map_unmark_light (Map* m)
{
	memset (m->light_mark, 0, (m->width * LIGHTTABLE_RES) * (m->height * LIGHTTABLE_RES) * (m->num_layers+1) * sizeof (int));
}

